/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2021.
 * Description: support undefinstr and memabort enhance feature
 * Author: huangshaobo <huangshaobo6@huawei.com>
 * Create: 2021-01-26
 */
#ifdef CONFIG_RTOS_KBOX
#include <linux/set_kbox_region.h>
#include <linux/rtos_sysctl.h>
#endif
#include <linux/rtos/dump_page_table.h>
#if defined(CONFIG_X86_64)
#include <asm/traps.h>
#elif defined(CONFIG_ARM64)
#include <asm/esr.h>
#elif defined(CONFIG_ARM)
#include <asm/ptrace.h>
#endif
#ifdef CONFIG_CACHE_ECC_RECORD_ENABLE
#include <linux/hal/cache_ecc_check.h>
#endif
#include <linux/rtos_exception_enhance.h>
#ifdef CONFIG_RTOS_DEBUG_UNDEFINSTR_ENHANCE_FILTER
#include <linux/rtos_undefinstr_enhance_filter.h>
#endif

#ifdef CONFIG_RTOS_KBOX
unsigned int __read_mostly exception_enhance_log_size;
static int exception_enhance_region = -1;
#define MAX_UINT (~0U)
#define MAX_LOG_SIZE	(MAX_UINT / 1024)

int proc_exception_enhance_log_size(struct ctl_table *table,
				    int write, void __user *buffer,
				    size_t *lenp, loff_t *ppos)
{
	int tmpid, ret;
	unsigned int pre_size, region_size;

	if (!write)
		return proc_dointvec_minmax(table, write, buffer, lenp, ppos);

	pre_size = exception_enhance_log_size;

	ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
	if (ret)
		return ret;

	if (exception_enhance_log_size == 0) {
		exception_enhance_log_size = pre_size;
		return -EINVAL;
	}
	if (exception_enhance_log_size > MAX_LOG_SIZE) {
		pr_err("exception_enhance_log_size is too large!\n");
		exception_enhance_log_size = pre_size;
		return -EINVAL;
	}

	region_size = exception_enhance_log_size * 1024;
	tmpid = kern_kbox_register_region("exception_enhance", region_size, NULL);
	if (tmpid < 0) {
		exception_enhance_log_size = pre_size;
		pr_err("register exception_enhance kbox region failed with %d!\n",
			tmpid);
		return tmpid;
	}

	exception_enhance_region = tmpid;
	return 0;
}
#endif /* CONFIG_RTOS_KBOX */

static bool is_default_sighandle(int sig, struct task_struct *tsk)
{
	unsigned long flags;
	int blocked, ignored;
	struct k_sigaction *action = NULL;
	bool is_default;

	spin_lock_irqsave(&tsk->sighand->siglock, flags);
	action = &tsk->sighand->action[sig - 1];
	ignored = action->sa.sa_handler == SIG_IGN;
	blocked = sigismember(&tsk->blocked, sig);
	is_default = ignored || blocked || action->sa.sa_handler == SIG_DFL;
	spin_unlock_irqrestore(&tsk->sighand->siglock, flags);
	return is_default;
}

static void do_dump_pagetable(unsigned long pc)
{
#ifdef CONFIG_RTOS_KBOX
	int old_region_id = -1;

	if (exception_enhance_region > 0)
		old_region_id = set_kbox_region(exception_enhance_region);
#endif
	rtos_dump_pagetable(pc);
#ifdef CONFIG_RTOS_KBOX
	if (old_region_id >= 0)
		restore_kbox_region(old_region_id);
#endif
}

/*
 * Currently undefined instruction exceptions and memory abort
 * exceptions only involve the following four signals to cause
 * user mode program exit: SIGILL SIGSEGV SIGBUS SIGKILL
 */
static bool is_user_abort(int sig, struct task_struct *tsk)
{
	bool is_abort_sig;

	is_abort_sig = sig == SIGILL ||
		       sig == SIGSEGV ||
		       sig == SIGBUS ||
		       sig == SIGKILL;

	return (is_abort_sig) ? (is_default_sighandle(sig, tsk)) : (false);
}

static inline void user_mode_dump_pagetable(struct task_struct *tsk,
					    unsigned long pc, int sig)
{
	if (is_user_abort(sig, tsk))
		do_dump_pagetable(pc);
}

/*
 * the panic judgment of this function needs to synchronize
 * the judgment of panic in the die function of each architecture.
 * if the panic judgment of the die function changes, this
 * function needs to be manually synchronized.
 */
static bool is_kernel_panic(void)
{
	bool ret = false;
#if defined(CONFIG_X86_64)
	ret = in_interrupt() || panic_on_oops;
#elif defined(CONFIG_ARM64)
#ifdef CONFIG_CACHE_ECC_RECORD_ENABLE
	ret = in_interrupt() || panic_on_oops || panic_on_cacheecc;
#else
	ret = in_interrupt() || panic_on_oops;
#endif
#elif defined(CONFIG_ARM)
	ret = in_interrupt() || panic_on_oops;
#endif
	return ret;
}

static inline void kernel_mode_dump_pagetable(unsigned long pc)
{
	if (is_kernel_panic())
		do_dump_pagetable(pc);
}

static bool is_target_mode(unsigned long mode)
{
	bool ret = false;
#if defined(CONFIG_X86_64)
	ret = mode == X86_TRAP_UD ||
	      mode == X86_TRAP_PF;
#elif defined(CONFIG_ARM64)
	ret = mode == ESR_ELx_EC_DABT_LOW ||
	      mode == ESR_ELx_EC_DABT_CUR ||
	      mode == ESR_ELx_EC_IABT_LOW ||
	      mode == ESR_ELx_EC_IABT_CUR ||
	      mode == ESR_ELx_EC_UNKNOWN  ||
	      mode == ESR_ELx_EC_SYS64;
#elif defined(CONFIG_ARM)
	ret = mode == ABT_MODE ||
	      mode == UND_MODE;
#endif
	return ret;
}

void rtos_exception_dump_pagetable(struct task_struct *tsk, bool is_user_mode,
				   unsigned long pc, unsigned long mode,
				   int sig)
{
	if (!show_exception_enhance)
		return;
	if (!is_target_mode(mode))
		return;
#ifdef CONFIG_RTOS_DEBUG_UNDEFINSTR_ENHANCE_FILTER
	if (is_user_mode && skip_dump_info(pc, sig))
		return;
#endif
	if (is_user_mode)
		user_mode_dump_pagetable(tsk, pc, sig);
	else
		kernel_mode_dump_pagetable(pc);
}

#ifdef CONFIG_RTOS_KBOX
static struct ctl_table rtos_exception_sysctls[] = {
	{
		.procname       = "exception_enhance_log_size",
		.data           = &exception_enhance_log_size,
		.maxlen         = sizeof(unsigned int),
		.mode           = 0640,
		.proc_handler   = proc_exception_enhance_log_size,
	},
	{}
};

rtos_kernel_sysctls_proc_init(rtos_exception, rtos_exception_sysctls);
#endif
